/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.assetmanager.impl.endpoint;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.opencastproject.assetmanager.api.AssetManager.DEFAULT_OWNER;
import static org.opencastproject.util.MimeTypeUtil.Fns.suffix;
import static org.opencastproject.util.RestUtil.R.badRequest;
import static org.opencastproject.util.RestUtil.R.noContent;
import static org.opencastproject.util.RestUtil.R.notFound;
import static org.opencastproject.util.RestUtil.R.ok;
import static org.opencastproject.util.RestUtil.R.serverError;
import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
import org.opencastproject.assetmanager.api.Asset;
import org.opencastproject.assetmanager.api.AssetManager;
import org.opencastproject.assetmanager.api.Version;
import org.opencastproject.assetmanager.api.query.AQueryBuilder;
import org.opencastproject.assetmanager.api.query.AResult;
import org.opencastproject.mediapackage.MediaPackageImpl;
import org.opencastproject.util.MimeTypeUtil;
import org.opencastproject.util.data.Option;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestParameter.Type;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;
import com.entwinemedia.fn.P1;
import com.entwinemedia.fn.P1Lazy;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* A base REST endpoint for the {@link AssetManager}.
* <p>
* The endpoint provides assets over http (see {@link org.opencastproject.assetmanager.impl.HttpAssetProvider}).
* <p>
* No @Path annotation here since this class cannot be created by JAX-RS. Put it on the concrete implementations.
*/
@RestService(name = "assetManager", title = "AssetManager",
notes = {
"All paths are relative to the REST endpoint base (something like http://your.server/files)",
"If you notice that this service is not working as expected, there might be a bug! "
+ "You should file an error report with your server logs from the time when the error occurred: "
+ "<a href=\"http://opencast.jira.com\">Opencast Issue Tracker</a>"
},
abstractText = "This service indexes and queries available (distributed) episodes.")
public abstract class AbstractAssetManagerRestEndpoint {
protected static final Logger logger = LoggerFactory.getLogger(AbstractAssetManagerRestEndpoint.class);
public abstract AssetManager getAssetManager();
/**
* @deprecated use {@link #snapshot} instead
*/
@POST
@Path("add")
@RestQuery(name = "add", description = "Adds a media package to the asset manager. This method is deprecated in favor of method POST 'snapshot'.",
restParameters = {
@RestParameter(
name = "mediapackage",
isRequired = true,
type = Type.TEXT,
defaultValue = "${this.sampleMediaPackage}",
description = "The media package to add to the search index.")},
reponses = {
@RestResponse(
description = "The media package was added, no content to return.",
responseCode = SC_NO_CONTENT),
@RestResponse(
description = "There has been an internal error and the media package could not be added",
responseCode = SC_INTERNAL_SERVER_ERROR)},
returnDescription = "No content is returned.")
@Deprecated
public Response add(@FormParam("mediapackage") final MediaPackageImpl mediaPackage) {
return handleException(new P1Lazy<Response>() {
@Override public Response get1() {
getAssetManager().takeSnapshot(DEFAULT_OWNER, mediaPackage);
return noContent();
}
});
}
@POST
@Path("snapshot")
@RestQuery(name = "snapshot", description = "Take a versioned snapshot of a media package.",
restParameters = {
@RestParameter(
name = "mediapackage",
isRequired = true,
type = Type.TEXT,
defaultValue = "${this.sampleMediaPackage}",
description = "The media package to take a snapshot from.")},
reponses = {
@RestResponse(
description = "A snapshot of the media package has been taken, no content to return.",
responseCode = SC_NO_CONTENT),
@RestResponse(
description = "There has been an internal error and no snapshot could be taken.",
responseCode = SC_INTERNAL_SERVER_ERROR)},
returnDescription = "No content is returned.")
public Response snapshot(@FormParam("mediapackage") final MediaPackageImpl mediaPackage) {
return handleException(new P1Lazy<Response>() {
@Override public Response get1() {
getAssetManager().takeSnapshot(DEFAULT_OWNER, mediaPackage);
return noContent();
}
});
}
@DELETE
@Path("delete/{id}")
@RestQuery(name = "deleteSnapshots",
description = "Removes snapshots of an episode, owned by the default owner from the asset manager.",
pathParameters = {
@RestParameter(
name = "id",
isRequired = true,
type = Type.STRING,
description = "The media package ID of the episode whose snapshots shall be removed"
+ " from the asset manager.")},
reponses = {
@RestResponse(
description = "Snapshots have been removed, no content to return.",
responseCode = SC_NO_CONTENT),
@RestResponse(
description = "The episode does either not exist or no snapshots are owned by the default owner.",
responseCode = SC_NOT_FOUND),
@RestResponse(
description = "There has been an internal error and the episode could not be deleted.",
responseCode = SC_INTERNAL_SERVER_ERROR)},
returnDescription = "No content is returned.")
public Response delete(@PathParam("id") final String mediaPackageId) {
return handleException(new P1Lazy<Response>() {
@Override public Response get1() {
if (mediaPackageId != null) {
final AQueryBuilder q = getAssetManager().createQuery();
if (q.delete(AssetManager.DEFAULT_OWNER, q.snapshot()).where(q.mediaPackageId(mediaPackageId)).run() > 0) {
return noContent();
} else {
return notFound();
}
} else
return notFound();
}
});
}
@GET
@Produces(MediaType.TEXT_XML)
@Path("episode/{mediaPackageID}")
@RestQuery(name = "getLatestEpisode",
description = "Get the media package from the last snapshot of an episode.",
returnDescription = "The media package",
pathParameters = {
@RestParameter(
name = "mediaPackageID",
description = "the media package ID",
isRequired = true,
type = STRING)
},
reponses = {
@RestResponse(responseCode = SC_OK, description = "Media package returned"),
@RestResponse(responseCode = SC_NOT_FOUND, description = "Not found")
})
public Response getMediaPackage(@PathParam("mediaPackageID") final String mediaPackageId) {
return handleException(new P1Lazy<Response>() {
@Override public Response get1() {
final AQueryBuilder q = getAssetManager().createQuery();
final AResult r = q.select(q.snapshot())
.where(q.mediaPackageId(mediaPackageId).and(q.version().isLatest()))
.run();
if (r.getSize() == 1) {
return ok(r.getRecords().head2().getSnapshot().get().getMediaPackage());
} else if (r.getSize() == 0) {
return notFound();
} else {
return serverError();
}
}
});
}
@GET
@Path("assets/{mediaPackageID}/{mediaPackageElementID}/{version}/{filenameIgnore}")
@RestQuery(name = "getAsset",
description = "Get an asset",
returnDescription = "The file",
pathParameters = {
@RestParameter(
name = "mediaPackageID",
description = "the media package identifier",
isRequired = true,
type = STRING),
@RestParameter(
name = "mediaPackageElementID",
description = "the media package element identifier",
isRequired = true,
type = STRING),
@RestParameter(
name = "version",
description = "the media package version",
isRequired = true,
type = STRING),
@RestParameter(
name = "filenameIgnore",
description = "a descriptive filename which will be ignored though",
isRequired = false,
type = STRING)},
reponses = {
@RestResponse(
responseCode = SC_OK,
description = "File returned"),
@RestResponse(
responseCode = SC_NOT_FOUND,
description = "Not found")})
public Response getAsset(@PathParam("mediaPackageID") final String mediaPackageID,
@PathParam("mediaPackageElementID") final String mediaPackageElementID,
@PathParam("version") final String version,
@HeaderParam("If-None-Match") final String ifNoneMatch) {
return handleException(new P1Lazy<Response>() {
@Override public Response get1() {
if (StringUtils.isNotBlank(ifNoneMatch)) {
return Response.notModified().build();
}
for (final Version v : getAssetManager().toVersion(version)) {
for (Asset asset : getAssetManager().getAsset(v, mediaPackageID, mediaPackageElementID)) {
final String fileName = mediaPackageElementID
.concat(".")
.concat(asset.getMimeType().bind(suffix).getOr("unknown"));
asset.getMimeType().map(MimeTypeUtil.Fns.toString);
// Write the file contents back
return ok(asset.getInputStream(),
Option.fromOpt(asset.getMimeType().map(MimeTypeUtil.Fns.toString)),
asset.getSize() > 0
? Option.some(asset.getSize())
: Option.<Long>none(),
Option.some(fileName));
}
// none
return notFound();
}
// cannot parse version
return badRequest("malformed version");
}
});
}
/** Unify exception handling. */
public static <A> A handleException(final P1<A> p) {
try {
return p.get1();
} catch (Exception e) {
logger.error("Error calling REST method", e);
throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
}